package de.mmis.core.sexpression.util.gui;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.Scrollable;

import org.apache.log4j.Logger;

import de.mmis.core.base.MethodDescriptor;
import de.mmis.core.base.ObjectDescriptor;
import de.mmis.core.base.abstracttree.InnerNode;
import de.mmis.core.base.abstracttree.LeafNode;
import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.abstracttree.TreeParserException;
import de.mmis.core.base.abstracttree.serializer.SerializerHelper;
import de.mmis.core.base.abstracttree.serializer.SerializerHelper.MethodInvocationException;
import de.mmis.core.base.abstracttree.serializer.SerializerHelper.MethodReturnValue;
import de.mmis.core.base.abstracttree.serializer.SerializerHelper.NoMatchingMethodFoundException;
import de.mmis.core.base.device.Device;
import de.mmis.core.base.event.Observable;
import de.mmis.core.base.gui.AbstractControlComponent;
import de.mmis.core.base.gui.DeviceControlComponent;
import de.mmis.core.sexpression.SExpressionParser;

/**
 * default {@link DeviceControlComponent} used for all types of {@link Device}.
 * It uses {@link Tree}s to describe method calls
 * 
 * @author Christian Koch
 * 
 */
public class SimpleControlComponent extends AbstractControlComponent<Object>
		implements Scrollable {

	private final static Logger LOGGER = Logger
			.getLogger(SimpleControlComponent.class);

	private static class Error extends Exception {

		public Error(String message) {
			super(message);
		}
	}

	public SimpleControlComponent() {
		super(0, Object.class, "Simple");
	}

	private JTextField methodResultTextField;

	private ObjectDescriptor objectDescriptor;

	Tree callMethod(Tree methodCall) throws Error {
		if (!getDevice().tryEnsureAvailability())
			throw new Error("device not available!");

		Tree result;
		try {
			MethodReturnValue mrv = SerializerHelper.invokeObjectMethod(
					objectDescriptor, methodCall, classLoader,
					new HashMap<String, Object>());

			result = mrv.toTree(true);
			if (result == null)
				result = new LeafNode(":void");

		} catch (MethodInvocationException e) {
			e.printStackTrace();
			throw new Error("#ERROR: Fehler beim Aufruf der Methode");
		} catch (NoMatchingMethodFoundException e) {
			e.printStackTrace();
			throw new Error("#ERROR: Methodenname nicht gefunden");
		} finally {
			getDevice().releaseAvailability();
		}

		return result;
	}

	@Override
	public void initialize(Class<?> clazz) {
		objectDescriptor = new ObjectDescriptor(getDevice().getDevice());

		initGUI();
	}

	private void initGUI() {
		LinkedList<MethodDescriptor> methods = new LinkedList<MethodDescriptor>();
		List<Method> objectMethods = Arrays.asList(Object.class.getMethods());

		for (final MethodDescriptor md : objectDescriptor
				.getMethodDescriptors()) {
			if (objectDescriptor.getGetterMethodDescriptors().contains(md))
				continue;

			if (objectMethods.contains(md.getMethod()))
				continue;

			methods.add(md);
		}

		GridBagLayout layout = new GridBagLayout();
		setLayout(layout);
		layout.columnWeights = new double[] { 0.0, 1.0 };
		layout.columnWidths = new int[] { 1, 1 };
		layout.rowWeights = new double[] { 0.0, 1.0 };
		layout.rowHeights = new int[] { 1, 1 };

		JPanel methodsPanel = new JPanel();
		GridBagLayout methodsLayout = new GridBagLayout();
		methodsPanel.setLayout(methodsLayout);
		methodsLayout.columnWeights = new double[] { 0.0, 1.0 };
		methodsLayout.columnWidths = new int[] { 1, 1 };
		methodsLayout.rowWeights = new double[methods.size()];
		methodsLayout.rowHeights = new int[methods.size()];
		for (int i = 0; i < methods.size(); i++)
			methodsLayout.rowWeights[i] = 1.0 / methods.size();

		final Insets insets = new Insets(5, 5, 5, 5);

		{
			JLabel methodResultLabel = new JLabel("Ergebnis: ");
			add(methodResultLabel, new GridBagConstraints(0, 0, 1, 1, 0, 0,
					GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
					insets, 0, 0));

			methodResultTextField = new JTextField();
			methodResultTextField.setEditable(false);
			add(methodResultTextField, new GridBagConstraints(1, 0, 1, 1, 0, 0,
					GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
					insets, 0, 0));

		}

		add(new JScrollPane(methodsPanel), new GridBagConstraints(0, 1, 2, 1,
				0, 0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
				insets, 0, 0));

		int i = -1;
		for (final MethodDescriptor md : methods) {
			i++;

			JPanel paramPanel = new JPanel();
			GridBagLayout paramLayout = new GridBagLayout();
			// first row: Parameter description
			// second row: button + text fields
			paramLayout.rowWeights = new double[] { 0.5, 0.5 };
			paramLayout.rowHeights = new int[] { 1, 1 };
			paramLayout.columnWeights = new double[md.getArgumentCount()];
			paramLayout.columnWidths = new int[md.getArgumentCount()];

			for (int j = 0; j < paramLayout.columnWeights.length; j++)
				paramLayout.columnWeights[j] = 1.0 / md.getArgumentCount();

			paramPanel.setLayout(paramLayout);

			// add first row: parameter help text
			for (int j = 0; j < md.getArgumentCount(); j++) {
				String param = md.getArgumentName(j)
						+ ": "
						+ DefaultControlComponent.classToSimpleString(md
								.getParameterType(j));
				JLabel paramLabel = new JLabel(param);
				paramLabel.setToolTipText(md.getArgumentHelp(j));

				paramPanel.add(paramLabel, new GridBagConstraints(j, 0, 1, 1,
						0, 0, GridBagConstraints.CENTER,
						GridBagConstraints.HORIZONTAL, insets, 0, 0));
			}

			// add Textfields for parameters
			final JTextField[] params = new JTextField[md.getArgumentCount()];
			for (int j = 0; j < md.getArgumentCount(); j++) {
				JTextField field = new JTextField();
				params[j] = field;
				paramPanel.add(field, new GridBagConstraints(j, 1, 1, 1, 0, 0,
						GridBagConstraints.CENTER,
						GridBagConstraints.HORIZONTAL, insets, 0, 0));
			}

			// add button
			JButton callButton = new JButton(md.getName());
			callButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					InnerNode methodCall = new InnerNode();
					methodCall.add(new LeafNode(md.getName()));

					for (int i = 0; i < md.getArgumentCount(); i++) {
						try {
							Tree param = SExpressionParser.parseUTF8(params[i]
									.getText());
							methodCall.add(param);
						} catch (TreeParserException e1) {
							methodResultTextField
									.setText("#ERROR: Kann S-Expression vom Parameter "
											+ md.getArgumentName(i)
											+ " nicht parsen");
							LOGGER.error(e1);
							return;
						}
					}

					try {
						Tree result = callMethod(methodCall);
						methodResultTextField.setText(result.toString());
					} catch (Error e1) {
						methodResultTextField.setText(e1.getMessage());
					}
				}
			});
			methodsPanel.add(callButton, new GridBagConstraints(0, i, 1, 1, 0,
					0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
					insets, 0, 0));

			methodsPanel.add(paramPanel, new GridBagConstraints(1, i, 1, 1, 0,
					0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
					insets, 0, 0));
		}
	}

	@Override
	public void notify(
			Observable<? extends de.mmis.core.base.gui.GUIComponent.GUIEvent> sender,
			de.mmis.core.base.gui.GUIComponent.GUIEvent event) {
		// do nothing
	}

	@Override
	public Dimension getPreferredScrollableViewportSize() {
		return getPreferredSize();
	}

	@Override
	public int getScrollableUnitIncrement(Rectangle visibleRect,
			int orientation, int direction) {
		return 10;
	}

	@Override
	public int getScrollableBlockIncrement(Rectangle visibleRect,
			int orientation, int direction) {
		return 50;
	}

	@Override
	public boolean getScrollableTracksViewportWidth() {
		// disable scrolling
		return true;
	}

	@Override
	public boolean getScrollableTracksViewportHeight() {
		// disable scrolling
		return true;
	}

}
